/* 
* linux-3.0.8/drivers/video/backlight/gt2440_backlight.c
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/slab.h>



#include "gt2440_bl.h"


/* data structure */
static const struct backlight_ops gt2440_backlight_ops = {
	.update_status	= gt2440_backlight_update_status,
	.get_brightness	= gt2440_backlight_get_brightness,
	.check_fb	= gt2440_backlight_check_fb,
};


/***** backlight ops function *****/
static int gt2440_backlight_update_status(struct backlight_device *backlight_dev)
{
	struct gt2440_backlight_info *info = dev_get_drvdata(&backlight_dev->dev);
	int brightness = backlight_dev->props.brightness;
	int max = backlight_dev->props.max_brightness;

	if (backlight_dev->props.power != FB_BLANK_UNBLANK)
		brightness = 0;
	if (backlight_dev->props.fb_blank != FB_BLANK_UNBLANK)
		brightness = 0;
	if (info->notify)
		brightness = info->notify(info->dev, brightness);
	if (brightness == 0) {
		pwm_config(info->pwm_dev, 0, info->period);
		pwm_disable(info->pwm_dev);
	} else {
		brightness = info->lth_brightness +
			(brightness * (info->period - info->lth_brightness) / max);
		pwm_config(info->pwm_dev, brightness, info->period);
		pwm_enable(info->pwm_dev);
	}
	return 0;
}
static int gt2440_backlight_get_brightness(struct backlight_device *backlight_dev)
{
	return backlight_dev->props.brightness;
}
static int gt2440_backlight_check_fb(struct backlight_device *backlight_dev,
				  struct fb_info *fb_inf)
{
	struct gt2440_backlight_info *info = dev_get_drvdata(&backlight_dev->dev);
	return !info->check_fb || info->check_fb(info->dev, fb_inf);
}



/***** probe and remove function *****/
static int gt2440_backlight_probe(struct platform_device *pdev)
{
	struct backlight_properties props;
	struct gt2440_backlight_platdata *pdata;
	struct gt2440_backlight_info *info;
	int ret;

	pdata = pdev->dev.platform_data;
	if (pdata == NULL) {
		dev_err(&pdev->dev,"failed to get platform data for backlight\n");
		return -EINVAL;
	}
	if (pdata->init) {
		ret = pdata->init(&pdev->dev);
		if (ret < 0){
			dev_err(&pdev->dev,"failed to init gpio pin for backlight\n");
			return ret;
		}
	}
	info = kzalloc(sizeof(struct gt2440_backlight_info), GFP_KERNEL);
	if(info == NULL){
		dev_err(&pdev->dev,"failed to allocate struct gt2440_backlight _info{} for backlight \n");
		ret = -ENOMEM;
		goto	exit_1;
	}

	info->dev = &pdev->dev;
	info->period = pdata->pwm_period_ns;
	info->lth_brightness = pdata->lth_brightness *(pdata->pwm_period_ns / pdata->max_brightness);

	info->notify = pdata->notify;
	info->check_fb = pdata->check_fb;
	platform_set_drvdata(pdev, info);
	
	info->pwm_dev = pwm_request(pdata->pwm_id, "backlight");
	if (IS_ERR(info->pwm_dev)) {
		dev_err(&pdev->dev, "failed to request PWM for backlight\n");
		ret = PTR_ERR(info->pwm_dev);
		goto exit_2;
	}

	memset(&props, 0, sizeof(struct backlight_properties));
	props.type = BACKLIGHT_RAW;
	props.max_brightness = pdata->max_brightness;
	info->backlight_dev = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, info,
				       &gt2440_backlight_ops, &props);
	if (IS_ERR(info->backlight_dev)) {
		dev_err(&pdev->dev, "failed to register backlight device: %d\n",ret);
		ret = PTR_ERR(info->backlight_dev);
		goto exit_3;
	}

	info->backlight_dev->props.brightness = pdata->dft_brightness;
	backlight_update_status(info->backlight_dev);

	
	return 0;

exit_3:
	pwm_free(info->pwm_dev);
exit_2:
	kfree(info);
exit_1:
	if (pdata->exit)
		pdata->exit(&pdev->dev);
	platform_set_drvdata(pdev, NULL);
	return ret;
}

static int gt2440_backlight_remove(struct platform_device *pdev)
{
	struct gt2440_backlight_platdata *pdata = pdev->dev.platform_data;
	struct gt2440_backlight_info *info = platform_get_drvdata(pdev);

	if(pdata == NULL)
		return 0;
	if(info == NULL)
		return 0;
	
	if(info->backlight_dev != NULL){
		backlight_device_unregister(info->backlight_dev);
		info->backlight_dev = NULL;
	}
	if(info->pwm_dev != NULL){
		pwm_config(info->pwm_dev, 0, info->period);
		pwm_disable(info->pwm_dev);
		pwm_free(info->pwm_dev);
		info->pwm_dev = NULL;
	}
	kfree(info);
	if (pdata->exit)
		pdata->exit(&pdev->dev);
	platform_set_drvdata(pdev, NULL);
	
	return 0;
}



/***** gt2440 board probe and remove function *****/
static int gt2440_board_backlight_probe(struct platform_device *pdev)
{	dprintk("gt2440_board_backlight_probe(id=%d)\n", pdev->id);

	return gt2440_backlight_probe(pdev);
}
static int gt2440_board_backlight_remove(struct platform_device *pdev)
{	dprintk("gt2440_board_backlight_remove(id=%d)\n", pdev->id);

	return gt2440_backlight_remove(pdev);
}

static struct platform_driver gt2440_backlight_platform_driver = {
	.probe		= gt2440_board_backlight_probe,
	.remove		= gt2440_board_backlight_remove,
	.driver		= {
		.name	= PLAT_DEVICE_NAME, /* "gt2440-rtc" */
		.owner	= THIS_MODULE,
	},
};


/***** init and exit ****/
static int __init gt2440_backlight_init(void)
{	dprintk("gt2440_backlight_init()\n");
	return platform_driver_register(&gt2440_backlight_platform_driver);
}

static void __exit gt2440_backlight_exit(void)
{	dprintk("gt2440_backlight_exit()\n");
	platform_driver_unregister(&gt2440_backlight_platform_driver);
}

module_init(gt2440_backlight_init);
module_exit(gt2440_backlight_exit);

MODULE_DESCRIPTION("GT2440 BackLight Device Driver");
MODULE_AUTHOR("Liguang13579<1659890447@qq.com>");
MODULE_LICENSE("GPL v2");
